'''
Epoll是linux才支持, windows不支持

EPOLLIN （可读）
EPOLLOUT （可写）
EPOLLET （ET模式）

epoll对文件描述符的操作有两种模式：
    LT（level trigger）和ET（edge trigger）。LT模式是默认模式，LT模式与ET模式的区别如下：

(1)LT模式：当epoll检测到描述符事件发生并将此事件通知应用程序，应用程序可以不立即处理该事件。
下次调用epoll时，会再次响应应用程序并通知此事件。

(2)ET模式：当epoll检测到描述符事件发生并将此事件通知应用程序，应用程序必须立即处理该事件。
如果不处理，下次调用epoll时，不会再次响应应用程序并通知此事件。

'''

import socket
import select

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("", 7788))
s.listen(10)
print(s.fileno())      # 文件描述符fd; server和client都是socket都有fd


epoll = select.epoll()
# 注册事件到epoll中
# epoll.register(fd[, eventmask])
# 注意，如果fd已经注册过，则会发生异常
# 将创建的套接字添加到epoll的事件监听中
epoll.register(s.fileno(), select.EPOLLIN|select.EPOLLET)

clients = {}
addresses = {}

while True:
    # epoll进行fd扫描--未指定超时时间则为阻塞等待
    epoll_list = epoll.poll()

    # 对事件进行判断
    for fd,events in epoll_list:
        # 如果是socket创建的套接字被激活
        if fd == s.fileno():
            newClient, addr = s.accept()
            clients[newClient.fileno()] = newClient
            addresses[newClient.fileno()] = addr
            # 向epoll中注册连接socket的可读事件
            epoll.register(newClient.fileno(), select.EPOLLIN|select.EPOLLET)

        elif events == select.EPOLLIN:
            recvData = clients[fd].recv(1024)   # 从激活fd上接收
            if len(recvData)>0:
                print(recvData)
            else:
                epoll.unregister(fd)
                clients[fd].close()
